#include "ShaderProgram.h"
#include "Texture2D.h"
#include "VertexArray.h"
#include "VertexBuffer.h"
#include "GlfwWindow.h"
#include "glm/ext/matrix_transform.hpp"
#include "glm/fwd.hpp"
#include "glm/trigonometric.hpp"

#include <iostream>
#include <string>
using namespace std;

int main(int argc, char *argv[])
{
    GlfwWindowManager window(1200, 900);
    if (!window.initWindow("LearnOpenGL")) {
        std::cout << "Failed to create GLFW window" << std::endl;
    }

    float vertices[] = {-0.5f, -0.5f, -0.5f, 0.0f,  0.0f,  -1.0f, 0.0f, 0.0f, 0.5f,  -0.5f, -0.5f, 0.0f,  0.0f,  -1.0f, 1.0f, 0.0f,
                        0.5f,  0.5f,  -0.5f, 0.0f,  0.0f,  -1.0f, 1.0f, 1.0f, 0.5f,  0.5f,  -0.5f, 0.0f,  0.0f,  -1.0f, 1.0f, 1.0f,
                        -0.5f, 0.5f,  -0.5f, 0.0f,  0.0f,  -1.0f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f,  0.0f,  -1.0f, 0.0f, 0.0f,

                        -0.5f, -0.5f, 0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f, 0.5f,  -0.5f, 0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,
                        0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f, 0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,
                        -0.5f, 0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f, -0.5f, -0.5f, 0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,

                        -0.5f, 0.5f,  0.5f,  -1.0f, 0.0f,  0.0f,  1.0f, 0.0f, -0.5f, 0.5f,  -0.5f, -1.0f, 0.0f,  0.0f,  1.0f, 1.0f,
                        -0.5f, -0.5f, -0.5f, -1.0f, 0.0f,  0.0f,  0.0f, 1.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f,  0.0f,  0.0f, 1.0f,
                        -0.5f, -0.5f, 0.5f,  -1.0f, 0.0f,  0.0f,  0.0f, 0.0f, -0.5f, 0.5f,  0.5f,  -1.0f, 0.0f,  0.0f,  1.0f, 0.0f,

                        0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f, 0.5f,  0.5f,  -0.5f, 1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
                        0.5f,  -0.5f, -0.5f, 1.0f,  0.0f,  0.0f,  0.0f, 1.0f, 0.5f,  -0.5f, -0.5f, 1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
                        0.5f,  -0.5f, 0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f, 0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

                        -0.5f, -0.5f, -0.5f, 0.0f,  -1.0f, 0.0f,  0.0f, 1.0f, 0.5f,  -0.5f, -0.5f, 0.0f,  -1.0f, 0.0f,  1.0f, 1.0f,
                        0.5f,  -0.5f, 0.5f,  0.0f,  -1.0f, 0.0f,  1.0f, 0.0f, 0.5f,  -0.5f, 0.5f,  0.0f,  -1.0f, 0.0f,  1.0f, 0.0f,
                        -0.5f, -0.5f, 0.5f,  0.0f,  -1.0f, 0.0f,  0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f,  -1.0f, 0.0f,  0.0f, 1.0f,

                        -0.5f, 0.5f,  -0.5f, 0.0f,  1.0f,  0.0f,  0.0f, 1.0f, 0.5f,  0.5f,  -0.5f, 0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
                        0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f, 0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
                        -0.5f, 0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f, -0.5f, 0.5f,  -0.5f, 0.0f,  1.0f,  0.0f,  0.0f, 1.0f};

    glm::vec3 cubePositions[] = {glm::vec3(0.0f, 0.0f, 0.0f),
                                 glm::vec3(2.0f, 5.0f, -15.0f),
                                 glm::vec3(-1.5f, -2.2f, -2.5f),
                                 glm::vec3(-3.8f, -2.0f, -12.3f),
                                 glm::vec3(2.4f, -0.4f, -3.5f),
                                 glm::vec3(-1.7f, 3.0f, -7.5f),
                                 glm::vec3(1.3f, -2.0f, -2.5f),
                                 glm::vec3(1.5f, 2.0f, -2.5f),
                                 glm::vec3(1.5f, 0.2f, -1.5f),
                                 glm::vec3(-1.3f, 1.0f, -1.5f)};

    glm::vec3 pointLightPositions[] = {
        glm::vec3(0.7f, 0.2f, 2.0f), glm::vec3(2.3f, -3.3f, -4.0f), glm::vec3(-4.0f, 2.0f, -12.0f), glm::vec3(0.0f, 0.0f, -3.0f)};

    VertexArray        vao;
    VertexBuffer       vbo(vertices, sizeof(vertices));
    VertexBufferLayout layout(8 * sizeof(float));
    layout.addAttribute(0, 3, GL_FLOAT, 0);
    layout.addAttribute(1, 3, GL_FLOAT, 3 * sizeof(float));
    layout.addAttribute(2, 2, GL_FLOAT, 6 * sizeof(float));
    vao.addBuffer(vbo, layout);

    VertexArray        lightVao;
    VertexBufferLayout lightLayout(8 * sizeof(float));
    lightLayout.addAttribute(0, 3, GL_FLOAT, 0);
    lightVao.addBuffer(vbo, lightLayout);

    Texture2D texture1("./textures/container2.png");
    Texture2D texture2("./textures/container2_specular.png");
    texture1.bind(0);
    texture2.bind(1);
    ShaderProgram lightingShader("./shaders/example_multlight/color_vertex.shader", "./shaders/example_multlight/color_fragment.shader");
    ShaderProgram lightCubeShader("./shaders/example_multlight/light_cube_vertex.shader",
                                  "./shaders/example_multlight/light_cube_fragment.shader");

    lightingShader.bind();
    lightingShader.setUniform1i("u_material.diffuse", 0);
    lightingShader.setUniform1i("u_material.specular", 1);
    lightingShader.setUniform1f("u_material.shininess", 32.0f);

    // 点光源颜色
    glm::vec3 lightColor(1.0f, 1.0f, 1.0f);

    // 启用深度测试
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
    window.loopRender([&]() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        auto cameraPos   = window.getCameraPos();
        auto cameraFront = window.getCameraFront();
        lightingShader.bind();

        lightingShader.setUniformVec3f("u_viewPos", cameraPos);

        // directional light
        lightingShader.setUniform3f("u_dirLight.direction", -0.2f, -1.0f, -0.3f);
        lightingShader.setUniform3f("u_dirLight.ambient", 0.05f, 0.05f, 0.05f);
        lightingShader.setUniform3f("u_dirLight.diffuse", 0.4f, 0.4f, 0.4f);
        lightingShader.setUniform3f("u_dirLight.specular", 0.5f, 0.5f, 0.5f);

        // point lights
        for (int i = 0; i < 4; i++) {
            std::string pointLightName = "u_pointLights[" + to_string(i) + "]";
            lightingShader.setUniformVec3f(pointLightName + ".position", pointLightPositions[i]);
            lightingShader.setUniform1f(pointLightName + ".constant", 1.0f);
            lightingShader.setUniform1f(pointLightName + ".linear", 0.09f);
            lightingShader.setUniform1f(pointLightName + ".quadratic", 0.032f);
            lightingShader.setUniform3f(pointLightName + ".ambient", 0.05f * lightColor.r, 0.05f * lightColor.g, 0.05f * lightColor.b);
            lightingShader.setUniform3f(pointLightName + ".diffuse", 0.8f * lightColor.r, 0.8f * lightColor.g, 0.8f * lightColor.b);
            lightingShader.setUniform3f(pointLightName + ".specular", 1.0f * lightColor.r, 1.0f * lightColor.g, 1.0f * lightColor.b);
        }

        // spot light properties
        lightingShader.setUniformVec3f("u_spotLight.direction", cameraFront);
        lightingShader.setUniformVec3f("u_spotLight.position", cameraPos);
        lightingShader.setUniform1f("u_spotLight.cutOff", glm::cos(glm::radians(12.5f)));
        lightingShader.setUniform1f("u_spotLight.outerCutOff", glm::cos(glm::radians(17.5f)));
        lightingShader.setUniform1f("u_spotLight.constant", 1.0f);
        lightingShader.setUniform1f("u_spotLight.linear", 0.09f);
        lightingShader.setUniform1f("u_spotLight.quadratic", 0.032f);
        lightingShader.setUniform3f("u_spotLight.ambient", 0.1f, 0.1f, 0.1f);
        lightingShader.setUniform3f("u_spotLight.diffuse", 0.8f, 0.8f, 0.8f);
        lightingShader.setUniform3f("u_spotLight.specular", 1.0f, 1.0f, 1.0f);

        // view/projection transformations
        lightingShader.setUniformMat4fv("u_proj", window.getProj());
        lightingShader.setUniformMat4fv("u_view", window.getView());

        vao.bind();
        for (unsigned int i = 0; i < 10; i++) {
            glm::mat4 model = glm::translate(glm::mat4(1.0f), cubePositions[i]);
            model           = glm::rotate(model, glm::radians(20.0f * i), glm::vec3(1.0f, 0.3f, 0.5f));
            lightingShader.setUniformMat4fv("u_model", model);

            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        lightCubeShader.bind();
        lightCubeShader.setUniformMat4fv("u_proj", window.getProj());
        lightCubeShader.setUniformMat4fv("u_view", window.getView());

        lightVao.bind();
        for (int i = 0; i < 4; i++) {
            glm::mat4 model = glm::translate(glm::mat4(1.0f), pointLightPositions[i]);
            model           = glm::scale(model, glm::vec3(0.2f));
            lightCubeShader.setUniformMat4fv("u_model", model);
            lightCubeShader.setUniformVec3f("u_color", lightColor);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }
    });
    return 0;
}